package com.starview.star.compiler.canonical.compile;

import com.starview.platform.data.EvaluationException;
import com.starview.platform.data.IValue;
import com.starview.platform.data.type.*;
import com.starview.platform.data.value.Factory;
import com.starview.star.code.repository.CodeCatalog;
import com.starview.star.compiler.ErrorReport;
import com.starview.star.compiler.cafe.Names;
import com.starview.star.compiler.cafe.compile.*;
import com.starview.star.compiler.canonical.*;
import com.starview.star.compiler.canonical.compile.cont.JumpContinue;
import com.starview.star.operators.assignment.runtime.GetRefValue.*;
import org.objectweb.asm.Opcodes;
import org.objectweb.asm.Type;
import org.objectweb.asm.tree.*;

import java.lang.reflect.Constructor;
import java.lang.reflect.Method;
import java.math.BigDecimal;
import java.math.BigInteger;
import java.util.List;

/**
 * Generate JVM code for expressions
 *
 * @author fgm
 */
public class ExpressionCompile implements
        TransformExpression<FrameState, FrameState, FrameState, FrameState, FrameState, CompileContext>{
  public static String EVAL_EXCEPT_SIG;
  public static String EQUAL_SIG;
  public static final boolean CHECK_NONNULL = false;

  static{

    try {
      Constructor<EvaluationException> evalExceptionCon = EvaluationException.class.getConstructor(IValue.class);
      EVAL_EXCEPT_SIG = org.objectweb.asm.Type.getConstructorDescriptor(evalExceptionCon);

      Method equalsMethod = Object.class.getDeclaredMethod("equals", Object.class);
      EQUAL_SIG = org.objectweb.asm.Type.getMethodDescriptor(equalsMethod);
    } catch (Exception e) {
      e.printStackTrace();
    }
  }

  @Override
  public FrameState transformApplication(Application app, CompileContext context){
    Location loc = app.getLoc();

    HWM hwm = context.getHwm();
    int mark = hwm.getDepth();
    InsnList ins = context.getIns();
    FrameState funState = app.getFunction().transform(this, context);

    IType funType = app.getFunction().getType();

    IContentExpression argTpl = app.getArgs();
    boolean enumeratedArgs = true;

    if (CanonUtils.isTuple(argTpl)) {
      List<IContentExpression> args = CanonUtils.constructorArgs(argTpl);

      if (args.size() < Theta.MAX_ARGS)
        compileArgs(loc, args, funType, context);
      else {
        ISpec argSpecs[] = SrcSpec.typeSpecs(TypeUtils.getFunArgTypes(funType), context.getDict(), context.getBldCat(),
                context.getErrors(), loc);

        argArray(args, argSpecs, context);
        enumeratedArgs = false;
      }
    } else {
      LabelNode nxLbl = new LabelNode();
      JumpContinue cont = new JumpContinue(nxLbl);
      FrameState argFrame = argTpl.transform(this, context.fork(cont));
      cont.jumpTarget(ins, argFrame, context);

      ins.add(new MethodInsnNode(Opcodes.INVOKEINTERFACE, Types.ICONSTRUCTOR, Constructors.GET_CELLS,
              Constructors.GETCELLS_INVOKESIG));
    }

    // actually invoke the function

    doLineNumber(loc, context);

    if (enumeratedArgs) {
      ISpec funSpec = funState.tos();
      String methodType = funSpec.getJavaType();
      // This is awful, but the JVM made me do it.
      if (methodType.startsWith(Types.FUN_PREFIX) || methodType.startsWith(Types.PRC_PREFIX))
        ins.add(new MethodInsnNode(Opcodes.INVOKEINTERFACE, methodType, Names.ENTER, funSpec.getJavaInvokeSig()));
      else
        ins.add(new MethodInsnNode(Opcodes.INVOKEVIRTUAL, methodType, Names.ENTER, funSpec.getJavaInvokeSig()));
    } else
      ins.add(new MethodInsnNode(Opcodes.INVOKEINTERFACE, Types.IFUNC, Names.ENTERFUNCTION,
              IFuncImplementation.IFUNCTION_INVOKE_SIG));

    hwm.reset(mark);
    FrameState reslt = context.getFrame().pushStack(
            SrcSpec.typeSpec(loc, TypeUtils.getFunResultType(funType), context.getDict(), context.getBldCat(), context
                    .getErrors()));
    return context.cont(reslt, loc);
  }

  void doLineNumber(Location loc, CompileContext context){
    MethodNode mtd = context.getMtd();
    if (!loc.equals(Location.nullLoc)) {
      LabelNode lnLbl = new LabelNode();
      mtd.instructions.add(lnLbl);
      mtd.instructions.add(new LineNumberNode(loc.getLineCount(), lnLbl));
    }
  }

//  private boolean compileArgs(Location loc, IType funType, IContentExpression argTpl, CompileContext cxt)
//  {
//    if (CanonUtils.isTuple(argTpl)) {
//      List<IContentExpression> args = CanonUtils.constructorArgs(argTpl);
//
//      if (args.size() < Theta.MAX_ARGS)
//        compileArgs(loc, args, funType, cxt);
//      else {
//        ISpec argSpecs[] = SrcSpec.typeSpecs(TypeUtils.getFunArgTypes(funType), cxt.getDict(), cxt.getBldCat(), cxt
//            .getErrors(), loc);
//
//        argArray(args, argSpecs, cxt);
//      }
//      return true;
//    } else {
//      LabelNode nxLbl = new LabelNode();
//      JumpContinue cont = new JumpContinue(nxLbl);
//      FrameState argFrame = argTpl.transform(this, cxt.fork(cont));
//      InsnList ins = cxt.getIns();
//      cont.jumpTarget(ins, argFrame, cxt);
//
//      ins.add(new MethodInsnNode(Opcodes.INVOKEINTERFACE, Types.ICONSTRUCTOR, Constructors.GET_CELLS,
//          Constructors.GETCELLS_INVOKESIG));
//
//      return false;
//    }
//  }

  @Override
  public FrameState transformRecord(RecordTerm record, CompileContext context){
    // Location loc = record.getLoc();
    //
    // Map<String, IContentExpression> els = record.getArguments();
    //
    // IType[] argTypes = new IType[els.size()];
    // IContentExpression[] args = new IContentExpression[els.size()];
    //
    // int ix = 0;
    // for (Entry<String, IContentExpression> entry : els.entrySet()) {
    // IContentExpression el = entry.getValue();
    // argTypes[ix] = el.getType();
    // args[ix++] = el;
    // }
    //
    // ISpec argSpecs[] = SrcSpec.typeSpecs(argTypes, context.getDict(), context.getBldCat(),
    // context.getErrors(), loc);
    //
    // if (els.size() < Theta.MAX_ARGS) {
    // for (ix = 0; ix < els.size(); ix++) {
    // IContentExpression arg = args[ix];
    //
    // LabelNode nxLbl = new LabelNode();
    //
    // CompileContext acxt = context.fork(new JumpCont(nxLbl));
    //
    // ISpec actual = arg.transform(this, acxt);
    // Utils.jumpTarget(context.getIns(), nxLbl);
    // if (argSpecs != null)
    // checkType(actual, argSpecs[ix], context);
    // }
    // } else
    // argArray(FixedList.create(args), argSpecs, context);
    //
    // return SrcSpec.typeSpec(loc, record.getType(), context.getDict(), context.getBldCat(),
    // context.getErrors());
    return null;
  }

  @Override
  public FrameState transformRecordSubstitute(RecordSubstitute update, CompileContext context){
    // TODO Auto-generated method stub
    return null;
  }

  @Override
  public FrameState transformCaseExpression(CaseExpression exp, CompileContext context){
    // TODO Auto-generated method stub
    return null;
  }

  @Override
  public FrameState transformCastExpression(CastExpression exp, CompileContext context){
    // final IType castType = exp.getType();
    // CafeDictionary dict = context.getDict();
    // CodeCatalog bldCat = context.getBldCat();
    // ErrorReport errors = context.getErrors();
    //
    // // we pretty much ignore a function type when casting to it...
    // final ISpec castSpec = TypeUtils.isProgramType(castType) ? SrcSpec.generic(exp.getLoc(),
    // castType, dict, bldCat,
    // errors) : SrcSpec.typeSpec(exp.getLoc(), castType, dict, bldCat, errors);
    //
    // IContinuation converter = new CastConverter(castType, context.getHwm(), castSpec);
    // IContinuation combo = new ComboCont(converter, context.getCont());
    //
    // CompileContext ccxt = context.fork(combo);
    // return exp.getInner().transform(this, ccxt);
    return null;
  }

  @Override
  public FrameState transformConditionalExp(ConditionalExp cond, CompileContext context){
    // LabelNode thLabel = new LabelNode();
    // LabelNode elLabel = new LabelNode();
    // InsnList ins = context.getIns();
    //
    // ReconcileCont reconcile = new ReconcileCont(context.getCont());
    //
    // Location loc = cond.getLoc();
    // doLineNumber(loc, context);
    //
    // CafeDictionary dict = context.getDict();
    // CafeDictionary thDict = dict.fork();
    //
    // ConditionContext ccxt = ConditionContext.fork(context, Sense.jmpOnFail, elLabel, thLabel);
    // cond.getCnd().transform(new ConditionCompile(), ccxt);
    //
    // Utils.jumpTarget(ins, thLabel);
    //
    // CompileContext thcxt = ccxt.fork(reconcile);
    // cond.getThExp().transform(this, thcxt);
    // dict.migrateFreeVars(thDict);
    // ins.add(elLabel);
    // CafeDictionary elDict = dict.fork();
    // CompileContext elCxt = thcxt.fork(elDict);
    // cond.getElExp().transform(this, elCxt);
    //
    // dict.migrateFreeVars(elDict);
    // thDict.dictUndo();
    // elDict.dictUndo();
    // return reconcile.getSpec();
    return null;
  }

  @Override
  public FrameState transformContentCondition(ContentCondition cond, CompileContext context){
    // LabelNode lf = new LabelNode();
    // LabelNode lx = new LabelNode();
    //
    // ConditionContext ccxt = ConditionContext.fork(context, Sense.jmpOnFail, lf, lx);
    // cond.getCondition().transform(new ConditionCompile(), ccxt);
    //
    // InsnList ins = context.getIns();
    // ins.add(new InsnNode(Opcodes.ICONST_1));
    // ins.add(new JumpInsnNode(Opcodes.GOTO, lx));
    // ins.add(lf);
    // ins.add(new InsnNode(Opcodes.ICONST_0));
    // ins.add(lx);
    //
    // return context.cont(SrcSpec.rawBoolSrc, cond.getLoc());
    return null;
  }

  @Override
  public FrameState transformMemo(MemoExp memo, CompileContext context){
    // TODO Auto-generated method stub
    return null;
  }

  @Override
  public FrameState transformNullExp(NullExp nil, CompileContext context){
    InsnList ins = context.getIns();
    ins.add(new InsnNode(Opcodes.ACONST_NULL));

    return context.cont(context.getFrame().pushStack(SrcSpec.voidSrc), nil.getLoc());
  }

  @Override
  public FrameState transformFunctionLiteral(FunctionLiteral f, CompileContext context){
    // TODO Auto-generated method stub
    return null;
  }

  @Override
  public FrameState transformLetTerm(LetTerm let, CompileContext context){
    // TODO Auto-generated method stub
    return null;
  }

  @Override
  public FrameState transformOverloaded(Overloaded over, CompileContext context){
    // TODO Auto-generated method stub
    return null;
  }

  @Override
  public FrameState transformOverloadedFieldAccess(OverloadedFieldAccess over, CompileContext context){
    // TODO Auto-generated method stub
    return null;
  }

  @Override
  public FrameState transformPatternAbstraction(PatternAbstraction pattern, CompileContext context){
    // TODO Auto-generated method stub
    return null;
  }

  public FrameState compileFieldAccess(Location loc, IContentExpression route, String field, IType fieldType,
                                       CompileContext context){
    LabelNode nxLbl = new LabelNode();

    JumpContinue jump = new JumpContinue(nxLbl);
    CompileContext recCxt = context.fork(jump);
    InsnList ins = context.getIns();

    FrameState recState = route.transform(this, recCxt);
    jump.jumpTarget(ins, recState, recCxt);
    ISpec rec = recState.tos();
    Utils.jumpTarget(ins, nxLbl);

    IType recordType = TypeUtils.deRef(route.getType());
    final ISpec fieldSpec;

    if (recordType instanceof TypeExp || recordType instanceof Type) {
      CafeDictionary dict = context.getDict();
      fieldSpec = dict.getFieldSpec(recordType, field);
      String getter = Types.getterName(dict.fieldJavaName(recordType, field));
      String javaRecordType = dict.javaName(recordType);

      checkCast(context, rec, javaRecordType);

      ins.add(new MethodInsnNode(Opcodes.INVOKEVIRTUAL, javaRecordType, getter, "()"
              + dict.javaFieldSig(recordType, field)));
    } else
    // We use the generic getMember method
    {
      context.bumpHwm(1);
      ins.add(new TypeInsnNode(Opcodes.CHECKCAST, Types.IRECORD));
      ins.add(new LdcInsnNode(field));
      ins.add(new MethodInsnNode(Opcodes.INVOKEINTERFACE, Types.IRECORD, "getMember", "(" + Types.JAVA_STRING_SIG + ")"
              + Types.IVALUE_SIG));
      fieldSpec = SrcSpec.generic(loc);
    }

    FrameState fState = context.getFrame().pushStack(fieldSpec);

    return context.cont(fState, loc);
  }

  @Override
  public FrameState transformFieldAccess(FieldAccess dot, CompileContext context){
    return compileFieldAccess(dot.getLoc(), dot.getRecord(), dot.getField(), dot.getType(), context);
  }

  @Override
  public FrameState transformRaiseExpression(RaiseExpression exp, CompileContext context){
    // InsnList ins = context.getIns();
    //
    // String exceptionType = Type.getInternalName(EvaluationException.class);
    // int mark = context.bumpHwm(2);
    // ins.add(new TypeInsnNode(Opcodes.NEW, exceptionType));
    // ins.add(new InsnNode(Opcodes.DUP));
    //
    // CompileContext ncxt = context.fork(new NullCont());
    // ISpec vspec = exp.getRaise().transform(this, ncxt);
    //
    // checkCast(context, vspec, Types.EVALUATION_EXCEPTION);
    //
    // ins.add(new MethodInsnNode(Opcodes.INVOKESPECIAL, exceptionType, Types.INIT,
    // EVAL_EXCEPT_SIG));
    // ins.add(new InsnNode(Opcodes.ATHROW));
    // context.resetHwm(mark);
    // return context.cont(SrcSpec.prcSrc, exp.getLoc());
    return null;
  }

  public static void checkCast(CompileContext cxt, ISpec spec, String javaType){
    if (!spec.getJavaType().equals(javaType)) {
      cxt.getIns().add(new TypeInsnNode(Opcodes.CHECKCAST, javaType));
    }
  }

  @Override
  public FrameState transformReference(Shriek ref, CompileContext context){
    Location loc = ref.getLoc();

    IContentExpression cell = ref.getReference();
    IType argType = cell.getType();

    assert TypeUtils.isReferenceType(argType);

    IType referType = TypeUtils.functionType(argType, TypeUtils.referencedType(argType));
    final IContentExpression refFun;

    if (TypeUtils.isRawBoolType(TypeUtils.referencedType(argType)))
      refFun = new Variable(loc, referType, GetRawBoolRef.name);
    else if (TypeUtils.isRawCharType(TypeUtils.referencedType(argType)))
      refFun = new Variable(loc, referType, GetRawCharRef.name);
    else if (TypeUtils.isRawIntType(TypeUtils.referencedType(argType)))
      refFun = new Variable(loc, referType, GetRawIntegerRef.name);
    else if (TypeUtils.isRawLongType(TypeUtils.referencedType(argType)))
      refFun = new Variable(loc, referType, GetRawLongRef.name);
    else if (TypeUtils.isRawFloatType(TypeUtils.referencedType(argType)))
      refFun = new Variable(loc, referType, GetRawFloatRef.name);
    else
      refFun = new Variable(loc, referType, GetRef.name);

    IContentExpression refApp = Application.apply(loc, TypeUtils.referencedType(argType), refFun, cell);

    return refApp.transform(this, context);
  }

  @Override
  public FrameState transformResolved(Resolved res, CompileContext context){
    Location loc = res.getLoc();
    IContentExpression resCall = Application.apply(loc, res.getType(), res.getOver(), res.getDicts());
    return resCall.transform(this, context);
  }

  @Override
  public FrameState transformScalar(Scalar scalar, CompileContext context){
    IValue val = scalar.getValue();
    IType type = val.getType();
    FrameState frame = context.getFrame();
    try {
      if (type.equals(StandardTypes.rawIntegerType)) {
        int ix = Factory.intValue(val);
        genIntConst(context, ix);
        return context.cont(frame.pushStack(SrcSpec.rawIntSrc), scalar.getLoc());
      } else if (type.equals(StandardTypes.rawLongType)) {
        long lx = Factory.longValue(val);
        genLongConst(context, lx);
        return context.cont(frame.pushStack(SrcSpec.rawLongSrc), scalar.getLoc());
      } else if (type.equals(StandardTypes.rawFloatType)) {
        double dx = Factory.floatValue(val);
        genFloatConst(context, dx);
        return context.cont(frame.pushStack(SrcSpec.rawDblSrc), scalar.getLoc());
      } else if (type.equals(StandardTypes.rawDecimalType)) {
        BigDecimal dx = Factory.decimalValue(val);
        genDecimalConst(context, dx);
        return context.cont(frame.pushStack(SrcSpec.rawDecimalSrc), scalar.getLoc());
      } else if (type.equals(StandardTypes.rawCharType)) {
        int ix = Factory.charValue(val);
        genIntConst(context, ix);
        return context.cont(frame.pushStack(SrcSpec.rawCharSrc), scalar.getLoc());
      } else if (type.equals(StandardTypes.rawStringType)) {
        String str = Factory.stringValue(val);
        context.getIns().add(new LdcInsnNode(str));
        context.bumpHwm(1);
        return context.cont(frame.pushStack(SrcSpec.rawStringSrc), scalar.getLoc());
      } else {
        context.reportError("invalid form of scalar", scalar.getLoc());
        InsnList ins = context.getIns();
        ins.add(new InsnNode(Opcodes.ACONST_NULL));

        return context.cont(frame.pushStack(SrcSpec.voidSrc), scalar.getLoc());
      }
    } catch (EvaluationException e) {
      context.reportError(e.getMessage(), scalar.getLoc());
      return context.cont(frame.pushStack(SrcSpec.voidSrc), scalar.getLoc());
    }
  }

  public static void genIntConst(CompileContext cxt, int ix){
    InsnList ins = cxt.getIns();
    switch (ix) {
      case -1:
        ins.add(new InsnNode(Opcodes.ICONST_M1));
        break;
      case 0:
        ins.add(new InsnNode(Opcodes.ICONST_0));
        break;
      case 1:
        ins.add(new InsnNode(Opcodes.ICONST_1));
        break;
      case 2:
        ins.add(new InsnNode(Opcodes.ICONST_2));
        break;
      case 3:
        ins.add(new InsnNode(Opcodes.ICONST_3));
        break;
      case 4:
        ins.add(new InsnNode(Opcodes.ICONST_4));
        break;
      case 5:
        ins.add(new InsnNode(Opcodes.ICONST_5));
        break;
      default:
        ins.add(new LdcInsnNode(ix));
        break;
    }
    cxt.bumpHwm(1);
  }

  public static void genLongConst(CompileContext cxt, long ix){
    InsnList ins = cxt.getIns();
    if (ix == 0)
      ins.add(new InsnNode(Opcodes.LCONST_0));
    else if (ix == 1)
      ins.add(new InsnNode(Opcodes.LCONST_1));
    else
      ins.add(new LdcInsnNode(ix));

    cxt.bumpHwm(2);
  }

  public static void genFloatConst(CompileContext cxt, double dx){
    InsnList ins = cxt.getIns();
    if (dx == 0.0)
      ins.add(new InsnNode(Opcodes.DCONST_0));
    else if (dx == 1.0)
      ins.add(new InsnNode(Opcodes.DCONST_1));
    else
      ins.add(new LdcInsnNode(dx));

    cxt.bumpHwm(2);
  }

  // We have to build a BigDecimal in small pieces
  public static void genDecimalConst(CompileContext cxt, BigDecimal bx){
    InsnList ins = cxt.getIns();
    BigInteger bix = bx.unscaledValue();
    int scale = bx.scale();
    byte[] ixData = bix.toByteArray();
    int mark = cxt.bumpHwm(4);
    ins.add(new TypeInsnNode(Opcodes.NEW, Type.getInternalName(BigDecimal.class)));
    ins.add(new InsnNode(Opcodes.DUP));
    ins.add(new TypeInsnNode(Opcodes.NEW, Type.getInternalName(BigInteger.class)));
    ins.add(new InsnNode(Opcodes.DUP));
    genIntConst(cxt, ixData.length);
    cxt.bumpHwm(1);
    ins.add(new IntInsnNode(Opcodes.NEWARRAY, Opcodes.T_BYTE));
    for (int ix = 0; ix < ixData.length; ix++) {
      int markb = cxt.bumpHwm(1);
      ins.add(new InsnNode(Opcodes.DUP));
      genIntConst(cxt, ix);
      genIntConst(cxt, ixData[ix]);
      ins.add(new InsnNode(Opcodes.BASTORE));
      cxt.resetHwm(markb);
    }
    ins.add(new MethodInsnNode(Opcodes.INVOKESPECIAL, Type.getInternalName(BigInteger.class), Types.INIT, "([B)V"));
    genIntConst(cxt, scale);
    ins.add(new MethodInsnNode(Opcodes.INVOKESPECIAL, Type.getInternalName(BigDecimal.class), Types.INIT, "("
            + Type.getDescriptor(BigInteger.class) + "I)V"));
    cxt.resetHwm(mark);
    cxt.bumpHwm(1);
  }

  @Override
  public FrameState transformConstructor(ConstructorTerm tuple, CompileContext context){
    List<IContentExpression> args = tuple.getElements();
    Location loc = tuple.getLoc();
    CafeDictionary dict = context.getDict();
    CodeCatalog bldCat = context.getBldCat();
    ErrorReport errors = context.getErrors();
    InsnList ins = context.getIns();

    ISpec argSpcs[] = new ISpec[args.size()];
    for (int ix = 0; ix < args.size(); ix++) {
      IContentExpression arg = args.get(ix);
      argSpcs[ix] = SrcSpec.typeSpec(arg.getLoc(), arg.getType(), dict, bldCat, errors);
    }

    if (args.size() < Theta.MAX_ARGS) {
      for (int ix = 0; ix < args.size(); ix++) {
        IContentExpression arg = args.get(ix);

        JumpContinue jump = new JumpContinue(new LabelNode());
        CompileContext acxt = context.fork(jump);

        FrameState actual = arg.transform(this, acxt);

        jump.jumpTarget(ins, actual, acxt);

        checkType(actual.tos(), argSpcs[ix], context);
      }
    } else
      argArray(args, argSpcs, context);

    return context.getFrame().pushStack(SrcSpec.typeSpec(loc, tuple.getType(), dict, bldCat, errors));
  }

  public FrameState compileArgs(Location loc, List<IContentExpression> args, IType funType, CompileContext cxt){
    assert TypeUtils.isFunctionType(funType);

    ISpec argSpecs[] = SrcSpec.typeSpecs(TypeUtils.getFunArgTypes(funType), cxt.getDict(), cxt.getBldCat(), cxt
            .getErrors(), loc);

    CompileContext context = cxt;
    FrameState frame = cxt.getFrame();

    if (args.size() < Theta.MAX_ARGS) {
      for (int ix = 0; ix < args.size(); ix++) {
        IContentExpression arg = args.get(ix);

        JumpContinue jump = new JumpContinue(new LabelNode());
        context = context.fork(jump, frame);

        FrameState actual = arg.transform(this, context);

        jump.jumpTarget(cxt.getIns(), actual, context);

        if (argSpecs != null)
          checkType(actual.tos(), argSpecs[ix], context);
      }
      return context.getFrame();
    } else
      return argArray(args, argSpecs, cxt);
  }

  public FrameState argArray(List<IContentExpression> args, ISpec[] argSpecs, CompileContext cxt){
    // int arity = args.size();
    //
    // InsnList ins = cxt.getIns();
    //
    // genIntConst(cxt, arity);
    // ins.add(new TypeInsnNode(Opcodes.ANEWARRAY, Types.IVALUE));
    //
    // for (int ix = 0; ix < arity; ix++) {
    // IContentExpression arg = args.get(ix);
    //
    // LabelNode nxLbl = new LabelNode();
    // int mark = cxt.bumpHwm(1);
    // ins.add(new InsnNode(Opcodes.DUP));
    // genIntConst(cxt, ix);
    //
    // IContinuation argCont = new JumpCont(nxLbl);
    // if (CHECK_NONNULL)
    // argCont = new ComboCont(new NonNullCont(), argCont);
    //
    // Continue aCont = new JumpContinue(nxLbl);
    // CompileContext acxt = cxt.fork(aCont);
    //
    // ISpec actual = arg.transform(this, acxt);
    // Utils.jumpTarget(cxt.getIns(), nxLbl);
    // if (argSpecs != null)
    // checkType(actual, argSpecs[ix], cxt);
    //
    // ins.add(new InsnNode(Opcodes.AASTORE));
    // cxt.resetHwm(mark);
    // }
    return null;
  }

  public FrameState buildTuple(ConstructorTerm con, CompileContext cxt){
    // InsnList ins = cxt.getIns();
    // List<IContentExpression> args = con.getElements();
    // int arity = args.size();
    //
    // if (arity == 0) {
    // ins.add(new FieldInsnNode(Opcodes.GETSTATIC, Utils.javaInternalClassName(NTuple.class),
    // "$0Enum", Utils
    // .javaTypeSig(NTpl.class)));
    // } else {
    // String tupleJavaType = Utils.javaInternalClassName(NTpl.class);
    //
    // int mark = cxt.bumpHwm(1);
    //
    // int dictMark = cxt.getDict().dictMark();
    //
    // // preamble to access the appropriate constructor
    // ins.add(new TypeInsnNode(Opcodes.NEW, tupleJavaType));
    // ins.add(new InsnNode(Opcodes.DUP));
    // cxt.bumpHwm(2);
    //
    // argArray(args, null, cxt);
    //
    // ins.add(new MethodInsnNode(Opcodes.INVOKESPECIAL, tupleJavaType, Types.INIT, "(" +
    // Types.IVALUE_ARRAY_SIG + ")V"));
    //
    // cxt.getDict().dictUndo(dictMark);
    //
    // cxt.resetHwm(mark);
    // }
    // cxt.bumpHwm(1);
    // return cxt.cont(SrcSpec.generalSrc, con.getLoc());
    return null;
  }

  public static void checkType(ISpec actual, ISpec expected, CompileContext cxt){
    if (!TypeUtils.isTypeVar(expected.getType()))
      checkCast(cxt, actual, expected.getJavaType());
  }

  @Override
  public FrameState transformValofExp(ValofExp val, final CompileContext context){
    // final InsnList ins = context.getIns();
    // final LabelNode endLbl = new LabelNode();
    //
    // IContinuation valisCont = new ComboCont(context.getCont(), new JumpCont(endLbl));
    //
    // ActionCompile actions = new ActionCompile();
    // CompileContext scxt = context.fork(valisCont);
    //
    // FrameState spec = val.getAction().transform(actions, scxt);
    //
    // Utils.jumpTarget(ins, endLbl);
    //
    // return spec;
    return null;
  }

  @Override
  public FrameState transformVariable(Variable variable, CompileContext context){
    // String id = variable.getName();
    // Location loc = variable.getLoc();
    // VarInfo var = StatementCompile.varReference(context, id, variable.getLoc());
    //
    // if (var != null) {
    // if (!var.isInited())
    // context.reportError("accessing uninitiliazed variable: " + id + "\ndeclared at " +
    // var.getLoc(), loc);
    // else if (var.getWhere().equals(VarSource.staticMethod))
    // context.reportError("cannot treat static method " + id + " as a regular value", loc);
    // else
    // var.loadValue(context.getMtd(), context.getHwm(), context.getDict());
    //
    // return context.cont(var, loc);
    // } else if (Builtins.isBuiltinVar(id)) {
    // ICafeBuiltinVar builtin = Builtins.getBuiltinVar(id);
    // String classSig = Type.getDescriptor(builtin.getImplClass());
    // String owner = Type.getInternalName(builtin.getJavaOwner());
    // InsnList ins = context.getIns();
    //
    // ins.add(new FieldInsnNode(Opcodes.GETSTATIC, owner, builtin.getJavaName(), classSig));
    // context.bumpHwm(1);
    // return context.cont(builtin, loc);
    // } else {
    // context.reportError(id + " not defined", loc);
    // return context.cont(SrcSpec.prcSrc, loc);
    // }
    return null;
  }

  @Override
  public FrameState transformVoidExp(VoidExp exp, CompileContext context){
    InsnList ins = context.getIns();
    ins.add(new InsnNode(Opcodes.ACONST_NULL));

    return context.cont(context.getFrame().pushStack(SrcSpec.voidSrc), exp.getLoc());
  }
}
